const Redis = require("./redis");
const Param = require("./param");
const zlib = require("zlib");

module.exports = class Cache {
  /**
   * 缓存并获取结果（调用方）
   * @param {*} key 缓存关键词，拼接参数
   * @param {number} timeSecond 缓存秒数
   * @param {object} thisObject this指向的对象
   * @param {function} targetMethod 没有缓存时，执行目标。暂时支持返回文本、对象、数组
   * @param  {...any} args 没有缓存时，参数
   * @returns
   */
  static async get(key, timeSecond, thisObject, targetMethod, ...args) {
    let fullKey = key;
    args.forEach((nArg) => (fullKey += ":" + nArg));
    let savedData = await Redis.exec("get", fullKey);
    if (savedData == null) {
      let result = await targetMethod.apply(thisObject, args);
      if (!Param.isBlank(result)) {
        if (typeof result === "string") {
          await Redis.exec("setex", fullKey, timeSecond + "", result);
          return result;
        } else {
          throw new Error("只支持返回值是文本的方法");
        }
      }
    } else {
      console.log("Found cache", fullKey);
      return savedData;
    }
  }

  /**
   *
   * @param {string} keyPrefix 缓存前缀
   * @param {number} timeSecond 缓存时间
   * @param {*} args 直接传arguments，用于拼接缓存key
   * @param {function} fn 没有缓存的实现
   * @param {boolean} zip 是否压缩
   * @returns 优先返回对象
   */
  static async auto(keyPrefix, timeSecond, args, fn, zip = false) {
    let fullKey = keyPrefix;
    for (let i = 0; i < args.length; i++) {
      fullKey += ":" + args[i].toString();
    }
    // 先从本地缓存取
    let savedData = Cache._getLocal(fullKey);
    let hasLocal = false;
    if (Param.isBlank(savedData)) {
      savedData = await Redis.exec("get", fullKey);
      if (!Param.isBlank(savedData)) {
        // 本地缓存1分钟
        Cache._setLocal(fullKey, savedData, 60000);
      }
    } else {
      hasLocal = true;
    }
    if (savedData == null) {
      let result = await fn();
      if (!Param.isBlank(result)) {
        let redisContent = "";
        // 处理JSON
        if (result instanceof Object) {
          if (result.success === false) {
            console.log("Do not cache fail result");
          } else {
            redisContent = JSON.stringify(result);
          }
        } else {
          redisContent = result.toString();
        }
        if (redisContent != "") {
          if (Cache._needZip(redisContent, zip)) {
            redisContent = Cache._zip(redisContent);
          }
          await Redis.exec("setex", fullKey, timeSecond + "", redisContent);
          // 本地缓存1分钟
          Cache._setLocal(fullKey, redisContent, 60000);
        }
        return result;
      }
    } else {
      console.log("Found cache" + (hasLocal ? " local" : ""), fullKey);
      savedData = Cache._unzip(savedData);
      // 优先转成json
      if (savedData.startsWith("{") || savedData.startsWith("[")) {
        return JSON.parse(savedData);
      } else {
        return savedData;
      }
    }
  }

  static _zip(text) {
    const zipData = zlib.gzipSync(Buffer.from(text));
    return zipData.toString("base64");
  }

  static _unzip(content) {
    // 判断content是否是buffer
    let text = "";
    const buffer = Buffer.from(content, "base64");
    // 判断是否gzip
    if (buffer.length > 2 && buffer[0] === 0x1f && buffer[1] === 0x8b) {
      text = zlib.gunzipSync(buffer).toString();
    } else {
      text = content;
    }
    return text;
  }

  static _needZip(text, zip) {
    return zip || Buffer.byteLength(text) / 1024 > 1;
  }

  static setWebCache(res, expireSecond) {
    res.setHeader("Cache-Control", "max-age=" + expireSecond);
  }

  // 本地缓存
  static localCache = {};

  static _setLocal(key, value, ttl) {
    const expireTime = ttl ? Date.now() + ttl : null;
    Cache.localCache[key] = { value, expireTime };
  }

  static _getLocal(key) {
    const item = Cache.localCache[key];
    if (!item) return null;

    if (item.expireTime && Date.now() > item.expireTime) {
      delete Cache.localCache[key];
      return null;
    }

    return item.value;
  }
};
